/**
 * 
 */

"use strict";

var movable = movable || {};

/*
 * call for update pending animations
 */
movable.animLastUpdate = 0;
movable.pause = false;

movable.animUpdate = function(timeStamp) {

	var timeSpan = timeStamp - movable.animLastUpdate;

	if (movable.animLastUpdate && !movable.pause) {

		if (timeSpan > 2000) {
			// time out
			console
				.log('movable.animUpdate() --> animation watchdog, skip animation step with a time span of '
					+ timeSpan + 'ms');
		} else if (timeSpan >= 100) {
			// each 100 ms
			var instances = [];
			for ( var key in fields.by_id) {
				if (fields.by_id[key] instanceof movable.Movable)
					instances.push(fields.by_id[key]);
			}
			instances.foreach(function(i, value, first, last) {
				value.update(timeSpan / 1000);
			});
		} else
			return;
	}

	// remind time stamp
	movable.animLastUpdate = timeStamp;
};

movable.Movable = function(col, row, cls, direction) {
	if (col === undefined || row === undefined)
		return;
	// call super type constructor
	fields.Field.call(this, col, row, 'movable');
	if (typeof cls === 'string') {
		this.el.addClass(cls);
		this.cls.push(cls);
	}
	// adjust angle
	this.toDirection = direction || defs.Direction.UP;
	this.fromDirection = defs.CounterDirection[this.toDirection];
	var angle = (cls === undefined ? 0 : defs.MovableAngleByDirection[cls][this.toDirection] || 0);
	this.setInnerPos(new utils.Vec2A(this.vec2.x, this.vec2.y, angle));
	// set speed
	this.speed = 0;
	this.motion = undefined;
	this.ground = undefined;
	this.ground_type = fields.Ground;
	this.distance = undefined;
	this.clock = new utils.Clock();
	this.collider = undefined;
};
movable.Movable.prototype = new fields.Field();
movable.Movable.prototype.moving = function() {
	return this.speed !== 0 || (this.motion && this.motion.active());
};
movable.Movable.prototype.stop = function() {
	if (this.motion)
		this.motion.stop();
};
movable.Movable.prototype.move = function(reverse, a, v) {
	var acc = a || 1;
	var vel = v || 1;
	var rev = reverse || false;
	var foreRun = Math.floor(Math.abs(Math.pow(v, 2) / a)) + 1;

	var move = (function(mov_field, a, v, reverse, forerun) {
		return function() {
			(new motion.VelocityMovement(a, v, mov_field, reverse, forerun, undefined, undefined))
				.move();
		};
	})(this, acc, vel, rev, foreRun);

	if (this.motion !== undefined) {
		if (!this.moving()) {
			this.motion.interrupt();
			move();
		} else {
			if (this.speed < 0 ^ rev) {
				this.motion.onstop = move;
				this.stop();
			}
		}
	} else
		move();
};
movable.Movable.prototype.place = function() {
	fields.Field.prototype.place.call(this);
	this.collider = new collider.Collider(this, 0.4, 0.4, 0.2, 0.2, false);
};
movable.Movable.prototype.remove = function() {
	fields.Field.prototype.remove.call(this);
	if (this.collider)
		this.collider.remove();
};
movable.Movable.prototype.getBaseAngle = function(direction) {
	return defs.MovableAngleByDirection[this.cls.last()][direction] || 0;
};
movable.Movable.prototype.applyDistance = function(distance) {
	/* limit to maximum and zero */
	var dist = Math.max(0, Math.min(1, distance || (1 / 2)));
	var fwdDir = defs.CounterDirection[this.fromDirection];
	var baseAngle = this.getBaseAngle(fwdDir);
	var vec2a = this
		.getPlacePosAngle(this.fromDirection, this.toDirection, dist);
	vec2a = vec2a.addA(baseAngle);
	this.setInnerPos(vec2a);
	this.distance = dist;
	return dist;
};
movable.Movable.prototype.rotate = function(direction) {
	if (direction === 0 || this.moving())
		return false;
	var dir = direction % 5;
	this.toDirection = dir;
	this.fromDirection = defs.CounterDirection[dir];
	var angleChange = (defs.MovableAngleByDirection[cls][this.toDirection] - this.vec2.a) || 0;
	fields.Field.prototype.rotate.call(this, angleChange);
};
movable.Movable.prototype.replace = function(col, row, from, to, distance) {
	if (this.placed()) {
		fields.FieldPosition.prototype.remove.call(this);
		var c = this.column, r = this.row;
		this.setPos(col, row);
		if (this.canPlace()) {
			// replace
			this.fromDirection = from;
			this.toDirection = to;
			fields.FieldPosition.prototype.place.call(this);
			this.applyDistance(distance);
			return true;
		} else {
			// recover position
			this.setPos(c, r);
			fields.FieldPosition.prototype.place.call(this);
		}
	}
	return false;
};
movable.Movable.prototype.getNext = function() {
	return this.getCurrent().getNext(this.toDirection);
};
movable.Movable.prototype.getPrevius = function() {
	return this.getCurrent().getNext(this.fromDirection);
};
movable.Movable.prototype.getCurrent = function() {
	if (this.ground && this.ground.column === this.column
		&& this.ground.row === this.row)
		return this.ground;
	var elements = this.getFields();
	var ground_type = this.ground_type;
	var result = elements.foreach(function(i, value, first, last) {
		// ground there?
		if (value instanceof ground_type)
			return value;
	});
	this.ground = result;
	return result;
};
movable.Movable.prototype.getAny = function(c, r) {
	var current = this.getCurrent();
	if (!current)
		return null;
	var anyPos = new fields.FieldPosition(c, r);
	var siblings = this.getSiblings.call(anyPos, current);
	if (siblings && siblings.length > 0)
		return siblings[0];
	return null;
};
movable.Movable.prototype.update = function(secs) {
	return true;
};
movable.Movable.prototype.getActions = function() {
	var actions = [];
	var thisId = this.id;
	// stop action
	actions
		.push(new fields.Action(thisId, 'stop', [], '<div class="action_entry"><div class="action stop">STOP</div></div>'));
	this.actions = actions;
	return actions;
};

movable.Train = function(col, row, direction, cls) {
	movable.Movable.call(this, col, row, 'train', direction);
	this.ground_type = fields.Rail;
	// optional train class
	if (typeof cls === 'string') {
		this.el.addClass(cls);
		this.cls.push(cls);
	}
};
movable.Train.prototype = new movable.Movable();
movable.Train.prototype.canPlace = function() {
	var elements = this.getFields();
	var railFound = elements.foreach(function(i, value, first, last) {
		// rail there?
		if (value instanceof fields.Rail && value.directions.length > 0)
			return true;
		if (last)
			return false;
	}) || false;
	return railFound;
};
movable.Train.prototype.place = function() {
	if (!this.placed() && this.canPlace()) {
		var rail = this.getCurrent();
		var dirs = rail.directions;
		var fwd = this.toDirection;
		var from = this.fromDirection;
		var selectedDirs = dirs[0];
		// get the nearest possible direction
		dirs.foreach(function(i, value, first, last) {
			var fwdIdx = value.indexOf(fwd);
			var fromVal;
			if (fwdIdx >= 0) {
				fromVal = value[(fwdIdx + 1) % 2];
				selectedDirs = [ value[fwdIdx], fromVal ];
				if (fromVal === from)
					return i;
			}
		});
		// update direction values
		this.fromDirection = selectedDirs[1];
		this.toDirection = selectedDirs[0];
		// place field
		movable.Movable.prototype.place.call(this);
		// commit position and angle
		this.applyDistance();
	}
};
movable.Train.prototype.applyDistance = function(distance) {
	var rail = this.getCurrent();
	var railDist = rail.getDistance(this.fromDirection, this.toDirection);
	/* limit to maximum and zero */
	var dist = Math.max(0, Math.min(railDist, distance || (railDist / 2)));
	var fwdDir = defs.CounterDirection[this.fromDirection];
	var baseAngle = this.getBaseAngle(fwdDir);
	var vec2a = rail
		.getPlacePosAngle(this.fromDirection, this.toDirection, dist);
	vec2a = vec2a.addA(baseAngle);
	this.setInnerPos(vec2a);
	this.distance = dist;
	return dist;
};
movable.Train.prototype.rotate = function(dir_inc) {
	if (!this.placed()) {
		// not yet placed
		var inc = dir_inc || 1;
		var newFwd = (this.toDirection + inc) % 5;
		this.toDirection = newFwd;
		this.fromDirection = defs.CounterDirection[newFwd];
		var angle = defs.MovableAngleByDirection.train[newFwd] || 0;
		var vec2a = new utils.Vec2A(this.vec2.x, this.vec2.y, angle);
		this.setInnerPos(vec2a);
	} else if (!this.moving()) {
		// placed, allow direction flip
		var newFwd = this.fromDirection;
		this.fromDirection = this.toDirection;
		this.toDirection = newFwd;
		var railDist = this.getCurrent()
			.getDistance(this.fromDirection, this.toDirection);
		this.applyDistance(railDist - this.distance);
	}
};
movable.Train.prototype.update = function(secs) {
	if (this.moving()) {
		// each 250ms new steam animation, when speed is not zero, otherwise
		// each 1.5s
		var tonQ = false;
		if (this.speed.fuzzyIsNull())
			tonQ = this.clock.ton(3000);
		else
			tonQ = this.clock.ton(250);
		if (tonQ) {
			// calculate offset by angle
			var vec2a = new utils.Vec2A(0, 0, this.vec2.a);
			var anim = new animation.steam(this, vec2a.add(0.25).toVec2());
			anim.place();
		}
	} else {
		this.clock.reset();
	}
};
movable.Train.prototype.move = function(reverse) {
	movable.Movable.prototype.move.call(this, reverse, 0.75, 1.25);
};
